#    One Room And A Goblin Invasion
#    Copyright (C) 2014  Jannik Haberbosch
#
#    This program is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program.  If not, see <http://www.gnu.org/licenses/>.


import pygame
pygame.init()

import os
import os.path
import random
import copy
import json

from code.AnimatedSprite import AnimatedSprite
from code.Timer import Timer
from code.Player import Player
from code.Door import Door
from code.DoorHiding import DoorHiding
from code.Projectile import Projectile
from code.CollisionLine import CollisionLine
from code.Goblin import Goblin
from code import helpers
from code.ProjectileExplosion import ProjectileExplosion
from code.Garbage import Garbage
from code.Text import Text
from code.Button import Button

# Constants.
from code import game_constants
from code import player_constants
from code import goblin_constants
from code import doors_constants
from code import projectile_constants
from code import collision_constants
from code import collision_functions
from code import creation_functions

# Constants for the doors.
DOOR_IS_OPEN = 1
DOOR_IS_CLOSED = 2
DOOR_IS_OPENING = 3
DOOR_IS_CLOSING = 4



def main():
	# Set up a new window.
	window = pygame.display.set_mode(
		game_constants.WINDOW_DIMENSIONS,
		pygame.FULLSCREEN
		)


	# The background image shows the room with the kitchen, the bathroom, ... .
	# Setting the colorkey to white is not required.
	background_one_room_image = helpers.load_image(
		game_constants.IMAGE_ONE_ROOM,
		set_colorkey = False
	)
	
	# We need a rectangle in order to know where to place it.
	background_one_room_rect = pygame.Rect(
		(
			0,
			0,
			background_one_room_image.get_width(),
			background_one_room_image.get_height()
		)
	)


	# A clock is required to keep the game running at a specified frame-rate.
	clock = pygame.time.Clock()

	main_menu(
		window, clock, background_one_room_image, background_one_room_rect
	)

def help_menu( window, clock, background_one_room_image, background_one_room_rect 	):
	pygame.mouse.set_visible( True ) # TODO:  Outcomment this when done.
	pygame.event.set_grab( False ) # TODO:  Outcomment this when done.	
	
	menu_background_image = helpers.load_image(
		"menu_background.png"
	)
	menu_background_rect = menu_background_image.get_rect(
		topleft = ( 162, 100 )
	)

	heading = Text(
		"Help",
		( 476, 130 ),
		fontsize = 26,
		color = game_constants.BLUE1
	)

	txt_help = pygame.sprite.Group()
	cnt1 = 190
	helptext = "You enjoy to live in your room.  But suddenly there happens to be a goblin invasion.  They come\nthrough your three doors and try to turn your clean room into mess.  You've got to stop them!\nYour task is to prevent the invasions of goblins and their mess as long as you can. Once there\n are at least 30 goblins or 50 garbage pieces lying around on the floor, you've lost and the game is over.\nUse the WASD keys to move around and the mouse to turn around and change the direction. Using the\nc key on your keyboard, you can make use of your broom to clean the floor, use it again and you are using your\n weapon again. Walking automatically changes from using your broom to using your weapon.  Good luck!"
	for l in helptext.splitlines():
		txt = Text(
			l,
			( 476, cnt1 ),
			fontsize = 14,
			color = game_constants.BLUE1
		)
		txt.rect.left = ( 1024 - txt.image.get_width() ) // 2
		txt_help.add( txt )
		cnt1 += 30

	btn_exit = Button(
		"Ok!",
		( 497, 420 ),
		fontsize = 18,
		color = game_constants.BLUE1,
		color_active = game_constants.BLACK
	)

	
	running = True
	while running:
		eventlist = pygame.event.get()
		for event in eventlist:
			if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
				running = False

		# Update section.
		if btn_exit.update():		
			return "exit"

		window.blit(
			background_one_room_image,
			background_one_room_rect
		)

		window.blit(
			menu_background_image,
			menu_background_rect
		)

		window.blit(
			heading.image,
			heading.rect
		)
		txt_help.draw( window )
		window.blit(
			btn_exit.image,
			btn_exit.rect
		)

		pygame.display.update()
		
		clock.tick( 30 )


	
def ingame_menu( window, clock, background_one_room_image, background_one_room_rect ):
	"""
	"""

	pygame.mouse.set_visible( True ) # TODO:  Outcomment this when done.
	pygame.event.set_grab( False ) # TODO:  Outcomment this when done.	
	
	menu_background_image = helpers.load_image(
		"menu_background.png"
	)
	menu_background_rect = menu_background_image.get_rect(
		topleft = ( 162, 100 )
	)

	heading = Text(
		"Pause",
		( 476, 130 ),
		fontsize = 26,
		color = game_constants.BLUE1
	)

	btn_continue = Button(
		"Continue!",
		( 473, 200 ),
		fontsize = 18,
		color = game_constants.BLUE1,
		color_active = game_constants.BLACK	
	)

	btn_exit = Button(
		"Exit",
		( 497, 230 ),
		fontsize = 18,
		color = game_constants.BLUE1,
		color_active = game_constants.BLACK
	)

	
	running = True
	while running:
		eventlist = pygame.event.get()
		for event in eventlist:
			if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
				running = False

		# Update section.
		if btn_continue.update():
			pygame.mouse.set_visible( False ) # TODO:  Outcomment this when done.
			pygame.event.set_grab( True ) # TODO:  Outcomment this when done.
			return "continue"
		if btn_exit.update():		
			return "exit"

		window.blit(
			background_one_room_image,
			background_one_room_rect
		)

		window.blit(
			menu_background_image,
			menu_background_rect
		)

		window.blit(
			heading.image,
			heading.rect
		)
		window.blit(
			btn_continue.image,
			btn_continue.rect
		)
		window.blit(
			btn_exit.image,
			btn_exit.rect
		)

		pygame.display.update()
		
		clock.tick( 30 )


def main_menu( window, clock, background_one_room_image, background_one_room_rect ):

	menu_background_image = helpers.load_image(
		"menu_background.png"
	)
	menu_background_rect = menu_background_image.get_rect(
		topleft = ( 162, 100 )
	)

	heading = Text(
		"One Room And A Goblin Invasion",
		( 321, 130 ),
		fontsize = 26,
		color = game_constants.BLUE1
	)

	btn_startgame = Button(
		"Prevent an invasion!",
		( 432, 200 ),
		fontsize = 18,
		color = game_constants.BLUE1,
		color_active = game_constants.BLACK	
	)

	btn_statsofpastgames = Button(
		"Stats of past games",
		( 433, 230 ),
		fontsize = 18,
		color = game_constants.BLUE1,
		color_active = game_constants.BLACK
	)

	btn_help = Button(
		"Help",
		( 495, 260 ),
		fontsize = 18,
		color = game_constants.BLUE1,
		color_active = game_constants.BLACK
	)
	#btn_help.rect.left = ( 1024 - btn_help.image.get_width() ) // 2

	btn_endgame = Button(
		"Quit",
		( 495, 290 ),
		fontsize = 18,
		color = game_constants.BLUE1,
		color_active = game_constants.BLACK
	)
	
	running = True
	while running:
		eventlist = pygame.event.get()
		for event in eventlist:
			if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
				running = False

		# Update section.
		if btn_startgame.update():
			game(
				window,
				clock,
				background_one_room_image,
				background_one_room_rect
			)
		if btn_statsofpastgames.update():
			past_games_menu(
				window,
				clock,
				background_one_room_image,
				background_one_room_rect
			)
		if btn_help.update():
			help_menu(
				window,
				clock,
				background_one_room_image,
				background_one_room_rect
			)
		if btn_endgame.update():
			running = False

		window.blit(
			background_one_room_image,
			background_one_room_rect
		)

		window.blit(
			menu_background_image,
			menu_background_rect
		)

		window.blit(
			heading.image,
			heading.rect
		)
		window.blit(
			btn_startgame.image,
			btn_startgame.rect
		)
		window.blit(
			btn_statsofpastgames.image,
			btn_statsofpastgames.rect
		)
		window.blit(
			btn_help.image,
			btn_help.rect
		)
		window.blit(
			btn_endgame.image,
			btn_endgame.rect
		)

		pygame.display.update()
		
		clock.tick( 30 )

	pygame.display.quit()


def enter_name_menu( window, clock, background_one_room_image, background_one_room_rect,
	accuracy, hits, time ):

	menu_background_image = helpers.load_image(
		"menu_background.png"
	)
	menu_background_rect = menu_background_image.get_rect(
		topleft = ( 162, 100 )
	)

	heading = Text(
		"Game over!",
		( 445, 130 ),
		fontsize = 26,
		color = game_constants.BLUE1
	)

	txt_hits = Text(
		"Hits: %i" % hits,
		( 487, 190 ),
		fontsize = 18,
		color = game_constants.BLUE1
	)
	txt_hits.rect.left = ( 1024 - txt_hits.image.get_width() ) // 2

	txt_accuracy = Text(
		"Accuracy: %2i" % accuracy + "%",
		( 462, 220 ),
		fontsize = 18,
		color = game_constants.BLUE1
	)
	txt_accuracy.rect.left = ( 1024 - txt_accuracy.image.get_width() ) // 2
	
	txt_time = Text(
		"Time: %s" % time,
		( 455, 250 ),
		fontsize = 18,
		color = game_constants.BLUE1
	)
	txt_time.rect.left = ( 1024 - txt_time.image.get_width() ) // 2


	txt_entername = Text(
		"Please enter your name below",
		( 391, 350 ),
		fontsize = 18,
		color = game_constants.BLUE1
	)

	txt_yourname = Text(
		"Your name here",
		( 391, 390 ),
		fontsize = 20,
		color = game_constants.BLUE1
	)
	txt_yourname.rect.left = ( 1024 - txt_yourname.image.get_width() ) // 2

	btn_ok = Button(
		"Ok!",
		( 498, 440 ),
		fontsize = 18,
		color = game_constants.BLUE1,
		color_active = game_constants.BLACK	
	)
	
	running = True
	while running:
		eventlist = pygame.event.get()
		for event in eventlist:
			if event.type == pygame.KEYDOWN:
				if event.key >= 97 and event.key <= 122:
					txt_yourname.update_text(
						txt_yourname.text + chr( event.key ).upper()
					)
					txt_yourname.rect.left = ( 1024 - txt_yourname.image.get_width() ) // 2
				elif event.key == 8 and len( txt_yourname.text ) > 0:
					txt_yourname.update_text(
						txt_yourname.text[:-1]
					)
					txt_yourname.rect.left = ( 1024 - txt_yourname.image.get_width() ) // 2

		# Update section.
		if btn_ok.update():
			running = False

		window.blit(
			background_one_room_image,
			background_one_room_rect
		)

		window.blit(
			menu_background_image,
			menu_background_rect
		)

		window.blit(
			heading.image,
			heading.rect
		)
		window.blit(
			txt_hits.image,
			txt_hits.rect
		)
		window.blit(
			txt_accuracy.image,
			txt_accuracy.rect
		)
		window.blit(
			txt_time.image,
			txt_time.rect
		)
		window.blit(
			txt_entername.image,
			txt_entername.rect
		)
		window.blit(
			txt_yourname.image,
			txt_yourname.rect
		)
		window.blit(
			btn_ok.image,
			btn_ok.rect
		)

		pygame.display.update()
		
		clock.tick( 30 )

	return txt_yourname.text


def past_games_menu( window, clock, background_one_room_image, background_one_room_rect ):

	highscore = read_score_from_file()
	if highscore is None:
		return

	
	menu_background_image = helpers.load_image(
		"menu_background.png"
	)
	menu_background_rect = menu_background_image.get_rect(
		topleft = ( 162, 100 )
	)

	heading = Text(
		"Past Games",
		( 445, 130 ),
		fontsize = 26,
		color = game_constants.BLUE1
	)
	heading.rect.left = ( 1024 - heading.image.get_width() ) // 2

	# highscore.
	highscore_lines = pygame.sprite.Group()
	posy = 160
	for l in highscore.splitlines():
		line = Text(
			l,
			( 100, posy ),
			fontsize = 14,
			color = game_constants.BLUE1
		)	
		line.rect.left = ( 1024 - line.image.get_width() ) // 2
		highscore_lines.add(
			line
		)
		posy += 20
		

	btn_ok = Button(
		"Ok!",
		( 498, 440 ),
		fontsize = 18,
		color = game_constants.BLUE1,
		color_active = game_constants.BLACK	
	)
	
	running = True
	while running:

		eventlist = pygame.event.get()

		# Update section.
		if btn_ok.update():
			running = False

		window.blit(
			background_one_room_image,
			background_one_room_rect
		)

		window.blit(
			menu_background_image,
			menu_background_rect
		)

		window.blit(
			heading.image,
			heading.rect
		)

		highscore_lines.draw( window )

		window.blit(
			btn_ok.image,
			btn_ok.rect
		)

		pygame.display.update()
		
		clock.tick( 30 )


def game( window, clock, background_one_room_image, background_one_room_rect ):

	

	pygame.mouse.set_visible( False ) # TODO:  Outcomment this when done.
	pygame.event.set_grab( True ) # TODO:  Outcomment this when done.


	# I need to blit the room now, because I need to grab the brown color from
	# the bottom-right pixel in order to hide part of the doors later on.
	# Otherwise the doorhandle would be visible from both sides when it is
	# closed which is not what I want.
	window.blit(
		background_one_room_image,
		background_one_room_rect
	)


	# Now it is time to create our main character which the player controls,
	# Mr. Clean.  Funny name, eh?
	mr_clean = Player(
		AnimatedSprite(
			player_constants.ANIMATION_WALKING_IMAGES, # the images.
			player_constants.POSITION_CENTER, # the starting position.
			Timer(
				*player_constants.ANIMATION_WALKING_TIMER_INFO # unpacking a tuple into arguments.
			) # controls how often the animation is updated.
		),
		AnimatedSprite(
			player_constants.ANIMATION_CLEANING_IMAGES,
			player_constants.POSITION_CENTER,
			Timer(
				*player_constants.ANIMATION_CLEANING_TIMER_INFO
			)
		),
		AnimatedSprite(
			player_constants.ANIMATION_STANDING_IMAGES,
			player_constants.POSITION_CENTER,
			Timer(
				*player_constants.ANIMATION_STANDING_TIMER_INFO
			)
		)
	)


	# Now I'm ready to create a few goblins for our game.  These will be placed
	# randomly onto the map for now.  Later on, they will come through one of
	# the three doors in the room.

	# All of our goblins are sprites and thus, we need a sprite-group to make
	# blitting and updating them easier.
	goblins = pygame.sprite.Group()
	
	# Creating the doors.
	door1 = Door(
		**doors_constants.DOOR1_DATA
	)
	door_hiding1 = DoorHiding(
		**doors_constants.DOOR1_HIDING_DATA
	)

	door2 = Door(
		**doors_constants.DOOR2_DATA
	)
	door_hiding2 = DoorHiding(
		**doors_constants.DOOR2_HIDING_DATA
	)

	door3 = Door(
		**doors_constants.DOOR3_DATA
	)
	door_hiding3 = DoorHiding(
		**doors_constants.DOOR3_HIDING_DATA
	)


	# Now it is time to set some class variables of the Projectile class.  This
	# class represents the bullets fired by the player (Mr. Clean).
	Projectile.load_image(
		projectile_constants.IMAGE
	)
	Projectile.set_speed(
		projectile_constants.SPEED
	)

	# This pygame.sprite.Group represents our projectile explosions.
	projectile_explosions = []


	# Now we need to define lines and rectangles which serve as obstacles.  The
	# player, the goblins and shots can not go through these.
	collision_rectangles = collision_constants.COLLISION_RECTANGLES
	collision_lines = collision_constants.COLLISION_LINES

	# The garbage.
	Garbage.load_images(
		img_broken_glasses = "broken_glasses.png",
		img_cutlery = "cutlery.png",
		img_soap = "soap.png",
		img_paper = "paper.png",
		img_dvd = "dvd.png",
		img_scattered_leaves = "scattered_leaves.png",
		img_socks = "socks.png"
	)

	Garbage.set_areas(
		coords_kitchen_area = ( 0, 0, 286, 567 ),
		coords_dining_area = ( 287, 0, 551, 441 ),
		coords_bathroom_area = ( 531, 0, 1024, 284 ),
		coords_living_room_area = ( 508, 459, 731, 768 ),
		coords_plants_area = ( 358, 568, 507, 768 ),
		coords_computer_area = ( 0, 568, 357, 768 ),
		coords_bedroom_area = ( 818, 285, 1024, 768 )
	)

	garbage = pygame.sprite.Group()

	# A boolean that indicates whether a game is running or not.
	running = True

	# Stats, score.

	# Maximum amount of goblins.
	max_goblins = 30
	max_garbage = 50

	accuracy = 0
	time = 0
	hits = 0
	fired_projectiles = 0

	texts = pygame.sprite.Group()
	txt_goblins_in_the_game = Text(
			"Goblins in the game: %s" % len( goblins ),
			( 750, 595 ),
			fontsize = 14,
			color = game_constants.WHITE
		)
	texts.add(
		txt_goblins_in_the_game
	)
	txt_garbage_in_the_game = Text(
		"Garbage in the game: %s" % len( garbage ),
		( 750, 610 ),
		fontsize = 14,
		color = game_constants.WHITE
	)
	texts.add(
		txt_garbage_in_the_game
	)
#	txt_accuracy = Text(
#		"Accuracy: -",
#		( 750, 625 ),
#		fontsize = 14,
#		color = game_constants.WHITE
#	)
#	texts.add(
#		txt_accuracy
#	)
	game_over = False

	stats_hour = 0
	stats_minutes = 0
	stats_seconds = 0

	time_counter = 0


	forced_quit = False

	while running:

		# Time measurement.
		time_counter += 1
		if time_counter == 30:
			time_counter = 0
			stats_seconds += 1
		
		if stats_seconds >= 60:
			stats_seconds = 0
			stats_minutes += 1

		if stats_minutes >= 60:
			stats_minutes = 0
			stats_hours += 1


		# Game over:
		if len( goblins ) >= 30:
			running = False
			game_over = True
		if len( garbage ) >= max_garbage:
			running = False
			game_over = True
		
		# The eventlist contains a list of all events that have occured in the
		# last gameloop.
		eventlist = pygame.event.get()
		for event in eventlist:
			# The user can quit the game by hitting the ESC key.
			if event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE:
				result_ingamemenu = ingame_menu( window, clock, background_one_room_image, background_one_room_rect )
				if result_ingamemenu == "continue":
					break
				elif result_ingamemenu == "exit":
					running = False
					forced_quit = True

		if forced_quit:
			break


		# The goblins try to escape from projectiles shot by the player, of course.
		projectiles_rectangles = []
		for x in mr_clean.projectiles:
			projectiles_rectangles.append(
				x.rect
			)

		projectiles_rectangles.append(
			mr_clean.rect
		)

		# Update objects.
		door1.update()
		if door1.state == DOOR_IS_OPEN and door1.let_goblin_walk_through:
			new_goblin = creation_functions.create_goblin(
				[ door1.rect.centerx + 35, door1.rect.centery + 50 ],
				20
			)
			goblins.add(
				new_goblin
			)
			# Install a timer for door 1 so it knows when to close itself.
			door1.close_door_timer = copy.deepcopy(
				new_goblin.walking_into_the_room_timer
			)
			door1.let_goblin_walk_through = False
			
		door2.update()
		if door2.state == DOOR_IS_OPEN and door2.let_goblin_walk_through:
			new_goblin = creation_functions.create_goblin(
				[ door2.rect.centerx - 40, door2.rect.centery - 30 ],
				135
			)
			goblins.add(
				new_goblin
			)
			# Install a timer for door 2 so it knows when to close itself.
			door2.close_door_timer = copy.deepcopy(
				new_goblin.walking_into_the_room_timer
			)
			door2.let_goblin_walk_through = False
					
		door3.update()
		if door3.state == DOOR_IS_OPEN and door3.let_goblin_walk_through:
			new_goblin = creation_functions.create_goblin(
				[ door3.rect.centerx + 70, door3.rect.centery + 10 ],
				65
			)
			goblins.add(
				new_goblin
			)
			# Install a timer for door 2 so it knows when to close itself.
			door3.close_door_timer = copy.deepcopy(
				new_goblin.walking_into_the_room_timer
			)
			door3.let_goblin_walk_through = False
					
		goblins.update(
			collision_rectangles,
			collision_lines,
			projectiles_rectangles
		)
		mr_clean.update(
			eventlist,
			collision_rectangles,
			collision_lines
		)
		mr_clean.projectiles.update(
			window.get_rect(),
			collision_rectangles,
			window
		)

		for x in projectile_explosions:
			if x.update():
				projectile_explosions.pop(
					projectile_explosions.index( x )
				)

		CollisionLine.collision_with_projectiles(
			collision_lines,
			mr_clean.projectiles
		)

		# Check for any collisions between goblins and projectiles.
		collided_projectiles, hits = collision_functions.collision_projectiles_goblins(
			mr_clean.projectiles,
			goblins,
			hits
		)
		collided_projectiles.extend( Projectile.test )
		collided_projectiles.extend( CollisionLine.test )
		creation_functions.create_projectile_explosions(
			collided_projectiles,
			projectile_explosions
		)
		collided_projectiles = []
		Projectile.test = []
		CollisionLine.test = []

		# Garbage.
		for goblin in goblins:
			if goblin.maybe_drop_garbage():
				# Drop new garbage.
				garbage.add(
					Garbage(
						goblin.rect.center
					)
				)


		collision_functions.collision_player_garbage(
			mr_clean,
			garbage
		)		

		# Draw object onto the window.
		window.blit(
			background_one_room_image,
			background_one_room_rect
		)
		garbage.draw(
			window
		)
		goblins.draw(
			window
		)
		window.blit(
			door1.image,
			door1.rect
		)
		window.blit(
			door_hiding1.image,
			door_hiding1.rect
		)
		window.blit(
			door2.image,
			door2.rect
		)
		window.blit(
			door_hiding2.image,
			door_hiding2.rect
		)
		window.blit(
			door3.image,
			door3.rect
		)
		window.blit(
			door_hiding3.image,
			door_hiding3.rect
		)
		CollisionLine.blit(
			window,
			collision_lines
		)
		mr_clean.projectiles.draw(
			window
		)
		for x in projectile_explosions:
			x.draw(
				window
			)

		window.blit(
			mr_clean.image,
			mr_clean.rect
		)

		# Blit texts.
		texts.draw(
			window
		)

		
		pygame.display.update()
		clock.tick(
			30
		)

		txt_goblins_in_the_game.update_text(
			"Goblins in the game: %s" % len( goblins )
		)

		txt_garbage_in_the_game.update_text(
			"Garbage in the game: %s" % len( garbage )
		)

		
		if Player.fired_projectiles > 0:
			accuracy = ( ( hits / Player.fired_projectiles ) * 100 )


	if game_over:
		pygame.mouse.set_visible( True ) # TODO:  Outcomment this when done.
		pygame.event.set_grab( False ) # TODO:  Outcomment this when done.
		time = "%02i:%02i:%02i" % ( stats_hour, stats_minutes, stats_seconds )
		player_name = enter_name_menu( window, clock, background_one_room_image, background_one_room_rect,
			accuracy, hits, time )

		write_score_to_file( player_name, accuracy, hits, time )
		
	pygame.mouse.set_visible( True ) # TODO:  Outcomment this when done.
	pygame.event.set_grab( False ) # TODO:  Outcomment this when done.


def write_score_to_file(
	player_name,
	accuracy,
	hits,
	time ):
	"""

	"""

	if not os.path.exists( "highscore" ):
		file_handle = open( "highscore", "w" )
	else:
		file_handle = open( "highscore", "a+" )

	file_handle.write(
		"%s: %s %s: %f%s %s: %s %s: %s\n" % ( "Name", player_name, "Accuracy", accuracy, "%", "Hits", hits, "Time", time )
	)

	file_handle.close()

	
def read_score_from_file():
	if not os.path.exists( "highscore" ):
		return None
	else:
		file_handle = open( "highscore", "r" )
		contents = file_handle.read()
		file_handle.close()
		return contents
	


# Run the game.
main()
